package DramScript
{
	import DramScript.IScriptLauncher;
	import DramScript.IScriptable;
	import utils.MoreMath;
	
	import flash.events.TimerEvent;
	
	import mx.core.Application;
	
	import utils.Trace;


	public class ScriptExecuter implements IScriptExecuter
	{
		// The object that launched this ScriptExecuter
		private var _scriptLauncher:IScriptLauncher;
		// This creates a compiler to load and parse the script
		private var _scriptCompiler:ScriptCompiler;
		public var scriptName:String;
		private static var _DramScriptCount:int = 0;
		private var _commandArr:Array ;// Command words
		private var _paramArr:Array; // Array of Parameter Arrays
		private var _commandTargetArr:Array; // Array of command targets: "game","actor","ui","script"
		private var _lineIndex:int; // Current line being executed or stored
		private var _varNumberArr:Array; // Array of arrays of numeric variable name, value pairs
		private var _varStringArr:Array; // Array of arrays of string variable name, value pairs
		private var _loopArr:Array; // Loop, Endloop counter stack
		// Debug variables
		private var _oldlineIndex:int = -1;

		// Constructor --------------------  ---------
		public function ScriptExecuter( scriptLauncher:IScriptLauncher, scriptType:String, scriptName:String, scriptDefinitionClass:Class    ) {
			trace("ScriptExecuter: "+scriptName);
			var scriptPathName:String;
			this.scriptName = scriptName;
			_DramScriptCount++;
			this._scriptLauncher = scriptLauncher;
			if( _scriptLauncher == null ){
				Trace.throwError("\nScriptExecuter constructor scriptLauncher==null");
			}
			if( scriptType != "fullpath"){
				scriptPathName = Application.application._assetPath +"scripts/"+scriptType+"/"+scriptName+".dram";
			} else {
				scriptPathName = scriptName;
			}
			this._commandArr = new Array() ;// Command word
			this._paramArr = new Array(); // Array of Parameter Arrays
			this._commandTargetArr = new Array();
			this._lineIndex = 0; // Current line being executed or stored
			this._varNumberArr = new Array();
			this._varStringArr = new Array();
			this._loopArr = new Array();
			this._scriptCompiler = new ScriptCompiler( scriptPathName, this, scriptDefinitionClass);
		} // DramScript constructor 
		public function executeCommand( target:String, command:String, paramArr:Array):ExecuteCommandResult {
			return new ExecuteCommandResult( );
		}
		// After DramScriptCompiler parses script it passes back arrays of commands, command targets, and params
		public function passParsedScript(commandArr:Array,paramArr:Array,commandTargetArr:Array, actorArr:Array):void { 
			trace("passParsedScript commandArr:" + commandArr);
					
			if((commandArr!=null) && (paramArr!=null)) {
				this._commandArr = commandArr;
				this._paramArr = paramArr;
				this._commandTargetArr = commandTargetArr;
				this._scriptLauncher.scriptLoadedNotification();
		   } else {
				Trace.throwError("ScriptExecuter.passParsedScript Script not valid");
		   } 
		}
		// runs the script ---------------------------------------------------------------
		public function tickTock(event:TimerEvent):void {
			var ecr:ExecuteCommandResult = new ExecuteCommandResult( ); 
			if( this._commandArr.length == 0 ){
				return; // Script not loaded yet
			}
 			if( this._lineIndex >= this._commandArr.length) {
				this._scriptLauncher.popScript();
				// Pop this script from the game's stack.
			} else {
				var command:String = this._commandArr[this._lineIndex];
				var tempArr:Array = this._paramArr[this._lineIndex];
				var paramArr:Array = processParams(tempArr);
				
				var commandTarget:String = this._commandTargetArr[this._lineIndex];
				if(this._lineIndex != this._oldlineIndex) { // To keep from repeating lines that are "wait"ing
 trace("ScriptExecuter.tickTock DramScript line "+this._lineIndex+": "+command+" "+paramArr);
					this._oldlineIndex = this._lineIndex;
				}
				switch( commandTarget) { // Who do we send the command to?
					case "script":
						switch(command) {
							case "elseif": // Goto next endif
								for(;this._lineIndex < this._commandArr.length; this._lineIndex++) {
									if(this._commandArr[this._lineIndex] == "endif"){
										break;
									}
								}
							break;
							case "endif": // Do nothing. Already error-checked
							break;
							case "loop": // Push loopcount and linenumber onto stack
								this._loopArr.push(new Array(paramArr[0],this._lineIndex));
							break;
							case "endloop": // Decrement loop count and jump back to loop+1 if more loopcount
											// pop the loop stack if last loop
								this._loopArr[this._loopArr.length - 1][0]--;
								if(this._loopArr[this._loopArr.length - 1][0] > 0) {
									this._lineIndex = this._loopArr[this._loopArr.length - 1][1];
								} else {
									this._loopArr.pop();
								}
							break;  
							case "ifrandom": // Jump a random # of endif's
								var randJump:int = randRange(0,paramArr[0]-1);
								this._lineIndex = findElseifCount(randJump);
							break; 
							case "trace":
								trace(paramArr[0]);
							break;
							default:
							break;
						} // switch(command) 
					break;
					case "uzer":
					case "game":
					case "thing":
					case "actor":
					case "ui":
						ecr = this._scriptLauncher.executeCommand(commandTarget, command,paramArr);
					break;
					case "preprocess":
						// Handled by the compiler
					break;
					default:
						Trace.throwError("Unrecognized commandTarget: "+commandTarget);
					break;
				} // switch (commandTarget) 
				if( ecr.resString == "BRANCH") {
					this._lineIndex = findElseifCount( ecr.branch );
				}
				if( ecr.resString != "WAIT") { // Unless we wait point to next line
					this._lineIndex++; 
				}
			} // if( _lineIndex >= _commandArr.length) 
		} // tickTock
		// Finds the "count" elseif on the same level as the starting line
		private function findElseifCount(count:int):int {
			var lineIndex:int = this._lineIndex;
			var ifCount:int = 0; // Keep track of nested ifs
			var elseCount:int = count;
			if(count > 0) { // if it's 0 then lineIndex is correct
				for(lineIndex = this._lineIndex+1; lineIndex < this._commandArr.length; lineIndex++ ) {
					switch (this._commandArr[lineIndex]) {
						case "elseif":
							if(ifCount==0) {
								elseCount--;
							}
						break;
						case "endif": 
							if(ifCount > 0) {
								ifCount--; 
							} 
						break;
						default: // Increment ifCount for any command that starts with "if"
							var ifIndex:int = this._commandArr[lineIndex].indexOf("if",0);
							if( ifIndex == 0) {
								ifCount++;
							}
						break;
					}
					if(elseCount == 0) {
						break;
					}
				
				} //for(var i = _lineIndex; i < _commandArr.length; i++ ) {
			}
			return lineIndex;
		}
		// processParams ----------------------------------------------------------
		// given an array of parameters, looks for variables and randominteger
		// any param that needs processing will be an array  {"varnumber","GODANGER"}, {"randominteger],n,m}
		private function processParams(paramArr:Array):Array {
			var finishedArr:Array = paramArr.slice(0); // to dereference _paramArr element which was passed
			for(var i:int = 0; i < paramArr.length; i++ ) {
				if(finishedArr[i] is Array) {
					finishedArr[i] = doRuntimeCalcs(paramArr[i]);
				}
			}
			return finishedArr;
		}
		// doRuntimeCalcs-------------------------------------------
		// Perform "built-in" functions such as randominteger(n,m)
		private function doRuntimeCalcs( calcArr:Array ):Number {
			var result_num:Number = NaN ;
			
			switch (calcArr[0]) {
				case "randominteger(":
					result_num=randRange(calcArr[1],calcArr[2]);
				break;
				case "varnumber":
					// getVar returns null if varnumber doesn't exist
//					result_num = Number(_game.uzer.dramVars.getVar("varnumber",calcArr[1]));
				break;
				default:
					Trace.throwError("DramScript.doRuntimeCalcs error: "+calcArr);
				break;
			}
			return result_num;
		} //doRuntimeCalcs
		
		private function randRange(minNum:Number, maxNum:Number):Number {
		return (Math.floor(Math.random()*(maxNum-minNum+1))+minNum);
		}
	}

}